package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.dao.GoodsDao;
import com.atguigu.gmall.list.service.ListService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.client.model.list.*;
import com.atguigu.gmall.product.client.model.product.BaseCategoryView;
import com.atguigu.gmall.product.client.model.product.BaseTrademark;
import com.atguigu.gmall.product.client.model.product.SkuAttrValue;
import com.atguigu.gmall.product.client.model.product.SkuInfo;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;


@Service
public class ListServiceImpl implements ListService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private GoodsDao goodsDao;
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RedisTemplate redisTemplate;

    //导入索引
    @Override
    public void upperGoods(Long skuId) {
        //一部手机全部索引手机
        Goods goods = new Goods();

        //1.根据skuId查询skuInfo信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        goods.setId(skuId);
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setTitle(skuInfo.getSkuName());
        goods.setPrice(skuInfo.getPrice().doubleValue()); //BigDecimal类型转换为double类型
        goods.setCreateTime(new Date());

        //2.根据tmId 查询品牌信息
        BaseTrademark baseTrademark = productFeignClient.getBaseTrademark(skuInfo.getTmId());
        goods.setTmId(baseTrademark.getId());
        goods.setTmName(baseTrademark.getTmName());
        goods.setTmLogoUrl(baseTrademark.getLogoUrl());

        //3.根据category3Id查询三级分类视图表
        BaseCategoryView baseCategoryView = productFeignClient.
                getBaseCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(baseCategoryView.getCategory1Id());
        goods.setCategory1Name(baseCategoryView.getCategory1Name());
        goods.setCategory2Id(baseCategoryView.getCategory2Id());
        goods.setCategory2Name(baseCategoryView.getCategory2Name());
        goods.setCategory3Id(baseCategoryView.getCategory3Id());
        goods.setCategory3Name(baseCategoryView.getCategory3Name());

        //4.根据skuId查询三表关联数据  主表sku_attr_value base_sku_info base_attr_value
        List<SkuAttrValue> skuAttrValueList =productFeignClient.getSkuAttrValue(skuId);
        List<SearchAttr> searchAttrList = skuAttrValueList.stream().map(skuAttrValue -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(skuAttrValue.getBaseAttrInfo().getId());
            searchAttr.setAttrName(skuAttrValue.getBaseAttrInfo().getAttrName());
            searchAttr.setAttrValue(skuAttrValue.getBaseAttrValue().getValueName()); //在mapper.xml文件进行的映射

            return searchAttr;
        }).collect(Collectors.toList());   //stream流的方式 偷天换日 将SkuAttrValue 转换为SearchAttr类型
        goods.setAttrs(searchAttrList);  // private List<SearchAttr> attrs;//平台属性集合

        //保存索引库
        goodsDao.save(goods);
    }
    //删除索引
    @Override
    public void lowerGoods(Long skuId) {
        //1：ES官方高级客户端的删除方式
  /*      DeleteRequest deleteRequest = new DeleteRequest();
        deleteRequest.index("goods");
        deleteRequest.id(skuId.toString());
        try {
            restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }*/
        //2：SpringDataES  删除方式   Mybatis-Plus 使用方式相同
        goodsDao.deleteById(skuId);
    }

    //增加热度  评分
    @Override
    public void hotScore(Long skuId) {
        //1. 通过缓存 进行缓冲  每10次 更新一次ES
        Double hotScore = redisTemplate.opsForZSet().incrementScore("hotScore", skuId, 1);
        if (hotScore%10==0){
            //查询ES库 此商品的评分
            Optional<Goods> byId = goodsDao.findById(skuId);
            Goods goods = byId.get();
            //将此商品评分加分
            goods.setHotScore(Math.round(hotScore));
            //将此商品保存回ES库
            goodsDao.save(goods);
        }

    }

    //开始搜索
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        SearchResponseVo vo =null;
        try {
            //1. 构建条件对象
            SearchRequest searchRequest=buildSearchRequest(searchParam);
            //2. 执行搜索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //3. 解析结果对象
             vo = parseSearchResponse(searchResponse);
            //当前页
            vo.setPageNo(searchParam.getPageNo());
            //每页数
            vo.setPageSize(searchParam.getPageSize());
            //总页数 == 总条数/每页数 +1
            //总页数 == (总条数+每页数 -1)/每页数
            vo.setTotalPages((vo.getTotal() + vo.getPageSize() -1) / vo.getPageSize());
        } catch (IOException e) {
            e.printStackTrace();
        }
                return vo;
    }

    //解析结果
    private SearchResponseVo parseSearchResponse(SearchResponse searchResponse) {
        SearchResponseVo vo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();
        Map<String, Aggregation> stringAggregationMap = searchResponse.getAggregations().asMap();

        //解析平台属性
        ParsedNested aggAttrs = (ParsedNested) stringAggregationMap.get("aggAttrs");
        ParsedLongTerms aggAttrId = aggAttrs.getAggregations().get("aggAttrId");
        List<SearchResponseAttrVo> attrsList = aggAttrId.getBuckets().stream().map(bucket -> {

            SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
            //平台属性Id
            attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));  //字符串转换为Long类型
            //平台属性名称
            ParsedStringTerms aggAttrName = bucket.getAggregations().get("aggAttrName");
            attrVo.setAttrName(aggAttrName.getBuckets().get(0).getKeyAsString());
            //平台属性值
            ParsedStringTerms aggAttrValue = bucket.getAggregations().get("aggAttrValue");
            List<String> attrValueList= aggAttrValue.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            attrVo.setAttrValueList(attrValueList);

                 return attrVo;

        }).collect(Collectors.toList());

        vo.setAttrsList(attrsList);

        //解析品牌集合
        ParsedLongTerms aggTmId = (ParsedLongTerms) stringAggregationMap.get("aggTmId");
        List<SearchResponseTmVo> trademarkList =
                aggTmId.getBuckets().stream().map(bucket -> {
                    SearchResponseTmVo tmVo = new SearchResponseTmVo();
                    tmVo.setTmId(Long.parseLong(bucket.getKeyAsString()));
                    //品牌名称
                    ParsedStringTerms aggTmName = bucket.getAggregations().get("aggTmName");
                    //品牌logo
                    ParsedStringTerms aggTmLogoUrl = bucket.getAggregations().get("aggTmLogoUrl");

                    tmVo.setTmName(aggTmName.getBuckets().get(0).getKeyAsString());
                    tmVo.setTmLogoUrl(aggTmLogoUrl.getBuckets().get(0).getKeyAsString());
                    return tmVo;
                }).collect(Collectors.toList());
        vo.setTrademarkList(trademarkList);

        //商品结果集
        SearchHit[] hits1 = hits.getHits();

        List<Goods> goodsList = Arrays.stream(hits1).map(hit -> {

        Goods goods = JSONObject.parseObject(hit.getSourceAsString(), Goods.class);
                //解析高亮的值
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();  //获取高亮集合
            if (highlightFields != null && highlightFields.size()>0){
                HighlightField title = highlightFields.get("title"); //获取显示高亮的字段
                if (title != null){
                    String hName = title.fragments()[0].toString();  //获取高亮的变量
                    goods.setTitle(hName); //给高亮字体赋值
                    System.out.println(hName);
                }
            }

            return goods;
        }).collect(Collectors.toList());
        vo.setGoodsList(goodsList);

        //分页 总条数
        System.out.println(hits.getTotalHits());
        vo.setTotal(hits.getTotalHits());

        return vo;
    }

    //构建搜索请求对象
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        // 条件对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //当多个条件的时候  组合条件对象   查询的时候只能出现一个query  需要把要查的信息都放在query里
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //1. 搜索的关键字
        String keyword = searchParam.getKeyword();
        if (!StringUtils.isEmpty(keyword)){                 //参数1 检索的字段 参数2 检索的变量  Operator.AND获取搜索关键字的交集
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",keyword).operator(Operator.AND));
        }else{
            //搜索全部数据
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }

        //2. 品牌  过滤条件  品牌id : 品牌名称
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            String[] t = StringUtils.split(trademark, ":"); //得到的品牌进行根据 : 进行分割
            boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",t[0])); //id是[0] 名称是[1]
        }

        //3. 一二三级分类
        Long category1Id = searchParam.getCategory1Id();
        if (category1Id!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",category1Id));
        }
        Long category2Id = searchParam.getCategory2Id();
        if (category1Id!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",category2Id));
        }
        Long category3Id = searchParam.getCategory3Id();
        if (category1Id!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",category3Id));
        }

        //4. 平台属性ID : 平台属性值 : 平台属性名称
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0){
            // 父  大爷
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //平台属性ID : 平台属性值 : 平台属性名称
            for (String prop : props) {
                String[] p = prop.split(":");   //StringUtils.split只能切割一个: 这个可以切割多个
                // 子
                BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",p[0]));
                subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",p[1]));
                boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));  //nestedQuery 嵌套
            }
            //父
            boolQueryBuilder.filter(boolQuery);
        }

        //构建条件对象  将把所有要查询的信息放在query里
        searchSourceBuilder.query(boolQueryBuilder);

        //5. 排序
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            String[] o = order.split(":");
            String field = null;
            switch (o[0]){
                case "1" : field ="hotScore";break;  //根据热度
                case "2" : field ="price";break;        //根据价格
            }
            searchSourceBuilder.sort(field,
                    o[1].equalsIgnoreCase(SortOrder.ASC.toString())?SortOrder.ASC:SortOrder.DESC);
        }


        //6. 分页  只能算是条件的一部分  不能全算条件
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        searchSourceBuilder.from((pageNo-1)*pageSize);   //开始行 limit 开始行  每页数
        searchSourceBuilder.size(pageSize); //每页个数

        //7. 高亮   手机是红色
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder
                .field("title")
                .preTags("<span style='color:red'>")
                .postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);


        //8. 品牌聚合
        searchSourceBuilder.aggregation(     //点击品牌的时候  显示的是图片 实则为字符串  id作为父 名字和图片作为子
                AggregationBuilders.terms("aggTmId").field("tmId")  //"tmId"
        .subAggregation(AggregationBuilders.terms("aggTmName").field("tmName"))  //"tmName"
        .subAggregation(AggregationBuilders.terms("aggTmLogoUrl").field("tmLogoUrl")));  //"tmLogoUrl"

        //9. 平台属性集合
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("aggAttrs","attrs")
                .subAggregation(AggregationBuilders.terms("aggAttrId").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("aggAttrName").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("aggAttrValue").field("attrs.attrValue"))));

        System.out.println(searchSourceBuilder.toString()); //打印出DSL语句

        SearchRequest searchRequest = new SearchRequest();  //请求ES
        //设置要搜索索引库的名称
        searchRequest.indices("goods");  //相当于mysql数据库
        searchRequest.types("info");    //mysql表
        //构建搜索条件对象
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

}
